//
// This is my little experiment with the Visual Studio SDK --
// the goal is to implement Matlab-style comment wrapping.  For
// those of you who haven't experienced the joys of Matlab's
// editor, the basic idea is that when you type inside a
// comment block, the words wrap more or less like they would
// inside a word-processor -- but the editor adds and deletes
// new inline comment prefixs as needed.  Visual Studio does
// have a word-wrapping option, but, because Visual Studio's
// wrapping isn't context sensitive, it tends to lead to uglier
// code. Besides which, if you assume word wrapping when
// writing comments anyone who doesn't have wrapping enabled
// will find your code almost unreadable.
// 
// The source code is pretty simple, and I've posted it at:
// sven.codeplex.com
// 
// Feel free to email me with any bugfixes :) 
// 
// In principle, writing a little VS extension to do
// Matlab-style word wrapping isn't that difficult.  You just
// create a text event listener by extending
// IVsTextLinesEvents, and create an
// IVsTextViewCreationListener that links it to any newly
// created text views, and then define an OnChangeLineText
// function that applies word wrapping as needed.
// 
// Unfortunately, the tool you're supposed to use to reset the
// current caret position from an event listener,
// IVsThreadSafeTextView, isn't supported by the text views
// created by visual studio 10.  And that's a problem, because
// it makes it difficult to preserve your typing position in a
// block of text after it's been word wrapped.
//
// While Noah Richards was kind enough to tell me about a
// workaround that allows thread-safe cursor positioning in the
// msvs10 editor, by the time I got his message, I'd already
// gone ahead and implemented a rather complicated and messy
// workaround.
//
// Said workaround involves making incremental changes that
// won't disrupt the current caret position, and then relying
// on later OnChange events to complete the wrapping.  This
// multi-pass logic gets rather convoluted -- but, it's now
// fairly carefully debugged, and seems to work just fine,
// assuming you're using a file with windows-style line
// endings.
// 
// If you want to add support for non-windows line endings,
// reimplementing most of the wrapping logic to take advantage
// of Noah's thread safe cursor techniques would almost
// certainly be a good idea. Noah's advice is to get your hands
// on a dispatcher object, and add an Action that can be 
// processed in the main UI thread.  For example:
//
//  // Given an IWpfTextView.VisualElement.Dispatcher, or some other Dispatcher for the UI thread
//  dispatcher.BeginInvoke(new Action(() => view.SetCaretPos(...)));
//
//
// The cstring wrapping code I added recently is implemented
// using such a dispatcher trick, and it does, indeed, work
// just fine.
//
//!     Known bugs
//
// In version 0.4, there was a bug that sometimes occurred
// when large numbers of files were open at once.  Tabbing back
// to an old file could cause exceptions went away once the
// file was reopened.  I think the bug was caused by my text
// buffer based dictionary scheme.  I've gotten rid of that, so 
// in theory, this should be fixed now.
//
//!     Extending the code
//
// Because of the multipass logic, this code's not as nice to
// work with as I'd like -- though one thing that should be
// easy to change is the set of inline comment prefixes that it
// recognizes.  At the moment, the regex I'm using to identify
// comments that should be wrapped is:
// 
//  @"^\s*((///*)|(---*))\s* \S"
// 
// That leads to reasonable behaviors in C++, Lua, and C#.  You
// could easily add a python-friendly term by changing to
// something like:
// 
//  @"^\s*((///*)|(---*)|(\#\#*))\s* \S"
// 
// Though the price would be some occasional weirdness when
// editing macros in C.
//
// Another thing that's fairly easy to change is the number of
// characters that the comments are wrapped to, or whether or
// not wrap limits should be set relative to the distance from
// the start of the line, or the distance from the start of the
// comment.  In fact, these variables are both the sorts of
// things that should probably be exposed in a
// tool configuration dialog menu -- but, I've already spent
// more time than I should playing around with msvs addons, so
// I may not ever get around to implementing a configuration
// dialog myself.
// 
namespace MultiLineComments
{

	using System;
    using System.Runtime.InteropServices;
	using System.Text.RegularExpressions;
	using System.Diagnostics;
	using System.Globalization;
	using System.ComponentModel.Design;
	using Microsoft.Win32;
	using Microsoft.VisualStudio.OLE.Interop;
	using Microsoft.VisualStudio;
	using Microsoft.VisualStudio.Shell;
	using Microsoft.VisualStudio.Shell.Interop;
	using Microsoft.VisualStudio.TextManager.Interop;
	using System.Windows.Forms;
	using Microsoft.VisualStudio.Text.Tagging;
	using System.ComponentModel.Composition;
	using Microsoft.VisualStudio.Text;
	using Microsoft.VisualStudio.Text.Classification;
	using Microsoft.VisualStudio.Text.Editor;
	using Microsoft.VisualStudio.Text.Formatting;
	using Microsoft.VisualStudio.Utilities;
	using Microsoft.VisualStudio.Editor;
    using System.Collections.Generic;
    using Microsoft.VisualStudio.Text.Operations;

    using System.Text;
    using System.Drawing;
	using EnvDTE;
    using EnvDTE80;


    public abstract class BaseWrapper : IVsTextLinesEvents
    {
        

        protected IVsEditorAdaptersFactoryService EditorFactory;

        //public _DTE dte;
        public SVsServiceProvider provider;

        public BaseWrapper(IVsTextView textViewAdapter, IVsEditorAdaptersFactoryService EditorFactory)
        {
            view = textViewAdapter;
            this.EditorFactory = EditorFactory;
        }

        
		public void OnChangeLineAttributes(int iFirstLine, int iLastLine)
		{
			// ignore attribute changes.
		}

        // general shared variables
       
        int cursor_line_offset;

        int cursor_wrapped_offset0;
        int cursor_wrapped_offset1;

        int wrapped_chars;

        protected IVsTextView view;

        protected int absolute_cursor;
        protected bool too_long_case;
        protected string prefix;
        protected string endline;
        protected string wrapped;

        //
        // main comment wrapping logic:
        //
        // this function is called by wrap_to() anytime it adds a new
        // line to the wrapped string.  it's relatively complex,
        // because i need to keeps track of where in the wrapped result
        // string the characters immediately before and after the
        // current caret position are inserted. in the vast majority
        // of cases, cursor_wrapped_offset0 will just be
        // cursor_wrapped_offset1-1, but, in those cases when a line is
        // split exactly at the current caret, keeping track of both
        // values will give us the information we need to deal with
        // the special case.
        //
        // i'm fairly sure there's at least one bug somewhere in the 
        // offset tracking logic, as i've found occasional cases where
        // the cursor offsets are off by +-1.  for the case of
        // windows style line endings (\r\n), i've been able
        // to fix these bugs by checking for cases in which solitary
        // \r's or \n's appear in the calculated substrings.
        // i'd prefer a more general purpose fix though.
        //
        // one odd thing about the offset tracking logic is that i've
        // made no attempt to get correct values for the offsets in the
        // special case when the caret is at the end of the current
        // paragraph.  that wrapping case is handled as a special case
        // by the multipass logic, one that does not reference the
        // cursor offsets.
        //
        public void add_line(string next_add, bool string_wrap)
        {

            wrapped_chars += next_add.Length;

            bool space_end = false;

            if (!string_wrap)
            {

                // if the line ends with a space, remove it. note that only the
                // final space is removed, so, if you put multiple spaces
                // between words, the multi-spacing should be preserved even as
                // the text is reformatted.
                if (next_add.EndsWith(" "))
                {
                    next_add = next_add.Remove(next_add.Length - 1, 1);
                    space_end = true;
                }
            }
            if (string_wrap)
            {
                next_add += "\"";
            }
           

            wrapped += prefix + next_add+endline;

            int wlen = wrapped.Length - endline.Length;
            if(space_end) wlen++;

            int pastcursor = wrapped_chars - cursor_line_offset;
            if (cursor_wrapped_offset0 == -1 &&
                pastcursor >= -1)
            {
                cursor_wrapped_offset0 = wlen - (pastcursor + 1);
            }
            if (cursor_wrapped_offset1 == -1 &&
                pastcursor >= 1
                )
            {
                cursor_wrapped_offset1 = wlen - (pastcursor);
            }
        }

        // here's where most of the real work of the comment wrapping
        // happens.
        public void wrap_to(string text, int num_chars, bool string_wrap)
        {

            string next_add = "";
            string old_text = text;
            wrapped_chars=0;
            too_long_case = false;

            cursor_wrapped_offset0 = cursor_wrapped_offset1 = -1;

            while(text.Length>0) {

                Match m = Regex.Match(text, @"^\S+");
                if (m.Success)
                {
                    string match = m.Value;

                    text = text.Remove(0, match.Length);

                    bool overflow = (next_add.Length == 0 && match.Length > num_chars);

                    if(overflow && wrapped.Length==0) {
                        too_long_case = true;
                        return;
                    }
                    // if the word doesn't overflow the allowed characters, add it to the
                    // current line.
                    if (match.Length + next_add.Length <= num_chars || overflow)
                    {
                        next_add += match;
                    }
                    // if the word doesn't overlap, then make it the start of a new line.
                    else
                    {

                        add_line(next_add, string_wrap);

                        next_add = match;
                    }

                    // add any trailing space
                    m = Regex.Match(text, @"^\s+");
                    if (m.Success)
                    {
                        string spaces = m.Value;

                        next_add += spaces;
                        
                        text = text.Remove(0,spaces.Length);
                    }

                    // now that the trailing spaces have made the line longer, we may 
                    // need to start up a new line. 
                    if (next_add.Length >= num_chars)
                    {
                        add_line(next_add, string_wrap);
                        next_add = "";
                    }

                }
                else
                {
                    // this case should never happen.
                    MessageBox.Show("hit a logic error in comment wrapping code.  you should be able to disable the multiline comment extension under Tools/Extension Manager.");
                    break;
                }
            }

            if (next_add.Length > 0) add_line(next_add, string_wrap);
        }

        // found this little keystate hack on:
        // http://crazorsharp.blogspot.com/2009/05/when-keypressed-keydown-just-isnt.html
        [DllImport("user32")]
        private static extern short GetKeyState(int vKey);
  
        private static int High(int keyState)
        {
            return keyState > 0 ? keyState >> 0x10 : (keyState >> 0x10) & 0x1;
        }

        public bool shift_press()
        {
            // for a list of key state id's, see:
            // http://msdn.microsoft.com/en-us/library/dd375731(VS.85).aspx
            short keyState = GetKeyState(0x10);
            int high = High(keyState);
            return high == 1;
        }


        // figure out if our view is the active view.
        bool is_active()
        {

            IVsTextManager txtManager = provider.GetService(typeof(SVsTextManager)) as IVsTextManager;

            // Get the active view from the Text Manager.
            IVsTextView active = null;
            txtManager.GetActiveView(1, null, out active);

            bool rval = active == view;

            return rval;
        }


     

        // more shared variables
        protected int r1, r0, c0, c1;
        protected  int ln;
        protected string line;
        protected int nlines;
        protected IVsTextLines lines;
        protected IWpfTextView wview;
        protected ITextBuffer wbuffer;
        protected Match m;
        protected int linestart;


        protected abstract bool wrapline(TextLineChange c);

        public virtual void pre_change_handling() { }

        public void set_new_caret(int newposition)
        {
            wview.VisualElement.Dispatcher.BeginInvoke(new Action(() =>
            {
                int pr, pc;

                if (view.GetLineAndColumn(newposition, out pr, out pc) == VSConstants.S_OK)
                {
                    view.SetCaretPos(pr, pc);
                }

            })
                      );

        }

        // the main entry point
		public void OnChangeLineText(TextLineChange[] pTextLineChange, int fLast)
		{

            // don't do anything if it looks like we're not the active text view.
            if(!is_active()) return;


            int wraplen = 60;

            try {


            // get some info about the cursor
            int pr, pc, vs;

            view.GetCaretPos(out pr, out pc);
            view.GetNearestPosition(pr, pc, out absolute_cursor, out vs);

            view.GetNearestPosition(pr, 0, out linestart, out vs);

            view.GetBuffer(out lines);
            lines.GetLineCount(out nlines);

            wview = EditorFactory.GetWpfTextView(view);
            wbuffer = EditorFactory.GetDataBuffer(lines);

            pre_change_handling();

            foreach (TextLineChange c in pTextLineChange)
            {

                r1 = c.iNewEndLine;
                r0 = c.iStartLine;
                c0 = c.iStartIndex;
                c1 = c.iNewEndIndex;

                // if this is an edit that has been triggered by some method
                // other than direct typing, we don't want to deal with it.
                if (pr != r1) continue;

                if (wrapline(c)) return;


                // 
                // Here we get into the main word wrapping cases.  If, as we
                // progress through the wrapping logic, we encounter evidence
                // that word wrapping is not appropriate, we use `continue` to
                // abort processing.  If the word wrapping logic eventually
                // causes us to edit the text, we call return, rather than
                // continue, on the grounds that trying to apply two different
                // text edits in response to the same OnChange event is always
                // bad.
                // 


                lines.GetLengthOfLine(r1, out ln);
                lines.GetLineText(r1, 0, r1, ln, out line);
                string firstline = line;

                // look to see if they're inside an inlined comment
//              m = Regex.Match(line, @"^\s*((///*)|(---*))\s* \S");
                m = Regex.Match(line, @"^\s*((///*)|(---*))\s* \S");
                if (!m.Success) continue;

                prefix = m.Value.Remove(m.Value.Length - 1);
                /*
                if (prefix.Length > wraplen - 5)
                {
                    // looks like we've found a weirdly long comment prefix -- lets not
                    // handle this case.
                    continue;
                }*/



                int r;

                // figure out what sort of line endings are appropriate, if we
                // can, otherwise, default to the windows standard. (there's
                // probably a way to figure out what line endings should be
                // used in a single line document, but, i don't know what it
                // is.)
                endline = "\r\n";
                if (nlines > r1+1)
                {
                    lines.GetLengthOfLine(r1, out ln);
                    lines.GetLineText(r1, ln, r1 + 1, 0, out endline);
                }
                else if (nlines > 1)
                {
                    lines.GetLengthOfLine(r1 - 1, out ln);
                    lines.GetLineText(r1 - 1, ln, r1, 0, out endline);
                }
                if (endline != "\r\n")
                {
                    MessageBox.Show("looks like your file doesn't use windows-style (\\r\\n) endlines.  that makes it incompatible with my MultilineComments extension.");
                    return;
                }


                //
                // here we get into the wrapping logic, which is necessarily a bit
                // complex. my approach is quite inefficient, however, which helps keep 
                // the logic simple.
                // 
                // the idea, basically, is that when someone runs over the wrap limit, we
                // need to find a word list that contains everything from the start of the
                // current line to the end of the current *paragraph*.
                //
                // we then delete all lines between our current position and the end of
                // the current paragraph, and replace them with lines calculated to wrap
                // the  paragraph.
                //
                // unfortunately, things are complicated by the need to preserve the
                // relative position of the caret -- i.e., if we trigger a word wrapping
                // event by typing 'b', our caret should be positioned after the 'b' once
                // the word wrapping has taken effect.
                //

                // in those odd cases when the cursor is at the start of the line,
                // consider it to be slightly offset.
                if (absolute_cursor <= linestart) absolute_cursor = linestart + prefix.Length;

                cursor_line_offset = absolute_cursor - linestart - prefix.Length;

                //
                // here we create longline, a string that contains the current paragraph
                //


                string longline = line.Remove(0, prefix.Length);
                for (r = r1 + 1; r < nlines; r++)
                {

                    lines.GetLengthOfLine(r, out ln);
                    lines.GetLineText(r, 0, r, ln, out line);

                    m = Regex.Match(line, "^" + prefix + @"\S");

                    if (m.Success)
                    {
                        longline += " " + line.Remove(0, prefix.Length);
                        if (r+1 != nlines)
                        {
                            string rendline;
                            lines.GetLineText(r, ln, r + 1, 0, out rendline);
                            if(rendline!="\r\n") {
                                MessageBox.Show("MultilineComments currently requires windows style line breaks, and it looks like your document contains breaks in some other style.  (It's possible that the presence of non-windows linebreaks is due to a bug in MultilineComments.)");
                                return;
                            }
                        }
                    }
                    else break;
                }

                if (wbuffer.EditInProgress) continue;
                //wbuffer.TakeThreadOwnership();

                wrapped = "";
                wrap_to(longline, wraplen,false);
                if (too_long_case) continue;

                // another weird case that we would prefer to ignore.
                if (cursor_line_offset < 1) continue;

                //
                // don't do anything if the wrapping is already happy with this line.
                //
                if (wrapped.StartsWith(firstline + endline)) continue;
                if (firstline.Length-prefix.Length <= wraplen &&
                    wrapped.StartsWith(firstline.Remove(firstline.Length - 1) + endline)) continue;




                // detect another occasionally important special case -- is the cursor
                // currently pointing at the last character in the file?
                bool last_character = false;
                if (nlines == r1 + 1)
                {
                    lines.GetLengthOfLine(r1, out ln);
                    if (ln == absolute_cursor - linestart) last_character = true;
                }

                // this is a good place to stick a breakpoint if you want to
                // debug the multipass logic -- as we now start dispatching the
                // different edit cases.
    
                string nextchar;
                lines.GetLineText(r1, absolute_cursor - linestart, r1 + 1, 0, out nextchar);
                
                // these are two important special cases that define how we handle things
                // when the cursor is the last character on the line.  
                if (last_character || nextchar == endline || nextchar == " " + endline)
                {

                    if (r > r1 + 1)
                    {
                        // in this case, we're somewhere inside a paragraph.  this is
                        // troublesome, because in order to avoid disturbing the cursor
                        // position, we need to leave the characters on either side of the
                        // cursor unchanged. but, if the two characters near the cursor
                        // are something like "a\r", we do need to change the "\r" to a 
                        // " " in order to do our word wrapping.  the only way to
                        // handle this case is to apply the wrapping in two passes, so
                        // that all we're doing here is deleting the troublesome
                        // carriage return.
                        // 
                        // of course, if the character that we *want* to follow the
                        // cursor position is a \r, we can actually apply a wrapping
                        // edit right now.

                        string cursor_chars;
                        lines.GetLineText(r1, absolute_cursor - linestart - 1, r1 + 1, 0, out cursor_chars);

                        string w0 = wrapped.Substring(cursor_wrapped_offset0, 1);
                        string w1 = wrapped.Substring(cursor_wrapped_offset1, 1);
                        string w0m = wrapped.Substring(cursor_wrapped_offset0-1, 1);

                        string offsetdelta = wrapped.Substring(cursor_wrapped_offset0, cursor_wrapped_offset1 - cursor_wrapped_offset0);


                        // i'm not confident that the case identification logic here 
                        // will work given single character endlines. in practice, i'm
                        // only using this extension with files that have window's
                        // style endlines, but, i'd prefer to have more general purpose
                        // code.

                        if (w1 == endline.Remove(1))
                        {
                            string pre_cursor = wrapped.Substring(0, cursor_wrapped_offset0);
                            string post_cursor = wrapped.Substring(cursor_wrapped_offset0 + 2);
                            string post_longline = longline.Substring(1 + cursor_line_offset);

                            post_cursor = post_cursor.Remove(post_cursor.Length - endline.Length);

                            string prevchar;
                            lines.GetLineText(r1, absolute_cursor - linestart - 1, r1, absolute_cursor - linestart + 1, out prevchar);

                            ITextEdit edit = wbuffer.CreateEdit();
                            if (edit == null) continue;
                            //pre_cursor = "**";
                            //post_cursor = "&&";
                            edit.Replace(linestart, absolute_cursor - linestart - 1, pre_cursor);
                            edit.Replace(absolute_cursor + 1, post_longline.Length, post_cursor);
                            edit.Apply();

                            //MessageBox.Show("not implemented yet.");
                        }
                        else if (cursor_chars.Length==2+endline.Length && cursor_chars.EndsWith(" " + endline) )
                        {
                            // in this case the line break wants to start before the the current
                            // cursor.  as best as i can tell, this case happens only when
                            // there's an unnecessary space at the end of a line.  so, the 
                            // simplest option is to just delete that space, and then let another
                            // wrapping pass handle it.
                            ITextEdit edit = wbuffer.CreateEdit();
                            edit.Delete(absolute_cursor, 1);
                            edit.Apply();
                        }
                        else
                        {
                            ITextEdit edit = wbuffer.CreateEdit();
                            if (edit == null) continue;
                            edit.Delete(absolute_cursor, endline.Length + prefix.Length - 1);
                            edit.Apply();
                        }

                        //IEditorOperations operations = opFactory.GetEditorOperations(wview);
                        //operations.MoveToPreviousCharacter(false);
                    }
                    else
                    {

                        // in this case, we're the last line of the paragraph so, if the
                        // user typed a space, we want to start a new line. (as the
                        // wrapping logic will have truncated that final trailing space).
                        // otherwise, we want to just insert the wrapped text. either
                        // way, a single insert operation will leave our caret where we
                        // want it to be.

                        int num_to_delete;
                        lines.GetLengthOfLine(r1, out num_to_delete);

                        ITextEdit edit = wbuffer.CreateEdit();
                        if (edit == null) continue;

                        string prevchar;
                        lines.GetLineText(r1, absolute_cursor - linestart - 1, r1, absolute_cursor - linestart, out prevchar);
                        if (prevchar == " ")
                        {
                            edit.Replace(linestart, num_to_delete, wrapped + prefix);
                        }
                        else
                            edit.Replace(linestart, num_to_delete, wrapped.Remove(wrapped.Length - endline.Length));

                        edit.Apply();
                    }

                }
                else
                {
                    // this is the case in which we know that the cursor isn't at
                    // the end of the line, so, any edits we apply need to preserve
                    // the characters on either side of the cursor.


                    // there's one more special case that it's nice to detect here:
                    // we need special handling for the case in which someone types
                    // a "delete" while at the end of a line that is inside a
                    // paragraph.  in that case, the carriage return between the
                    // cursor and the next line will just have been deleted, which
                    // leads to odd results.
                    if (c.iStartLine == c.iNewEndLine &&
                        c.iOldEndLine == c.iNewEndLine+1 && 
                        c.iOldEndIndex == 0 &&
                        c.iNewEndIndex  == c.iStartIndex)
                    {
                        string line_tail;
                        lines.GetLengthOfLine(r1, out ln);
                        lines.GetLineText(r1, absolute_cursor-linestart, r1, ln, out line_tail);
                        if (line_tail.StartsWith(prefix))
                        {
                            ITextEdit edit = wbuffer.CreateEdit();
                            if (edit == null) return;
                            edit.Delete(absolute_cursor, prefix.Length);
                            edit.Apply();
                            return;
                        }
                            
                    }


                    // if our paragraph has multiple lines, create an edit that
                    // will put the whole paragraph on a single line. we'll rely on
                    // the next OnChange event to finish the wrapping.  this
                    // application of multipass logic isn't necessary to preserve
                    // the cursor position, but, it does simplify debugging.
                    if (r > r1 + 1)
                    {

                        string total_lines;
                        lines.GetLengthOfLine(r - 1, out ln);
                        lines.GetLineText(r1, absolute_cursor - linestart + 1, r - 1, ln, out total_lines);

                        ITextEdit edit = wbuffer.CreateEdit();
                        if (edit == null) continue;
                        string post_cursor = longline.Substring(absolute_cursor - linestart - prefix.Length + 1);
                        edit.Replace(absolute_cursor + 1, total_lines.Length, post_cursor);
                        edit.Apply();
                    }
                    else
                    {

                        // ok, this is the case that most often ends up being the final
                        // pass of the word wrapping logic. in this case, the cursor is
                        // not at the end of the line, and the whole paragraph
                        // is contained on a single line.
                        string pre_cursor = wrapped.Substring(0, cursor_wrapped_offset0);
                        string post_cursor = wrapped.Substring(cursor_wrapped_offset0 + 2);
                        int longlinelen = longline.Length;
                        string post_longline = longline.Substring(1 + cursor_line_offset);

                        post_cursor = post_cursor.Remove(post_cursor.Length - endline.Length);

                        string offsetdelta = wrapped.Substring(cursor_wrapped_offset0, cursor_wrapped_offset1 - cursor_wrapped_offset0);
                        
                        // this is a dirty fix for a bug that
                        // depends on endline == "\r\n"
                        if (endline=="\r\n" && offsetdelta.StartsWith("\n"))
                        {
                            offsetdelta = "\r"+offsetdelta;
                        }

                        // another such dirty fix
                        if (endline == "\r\n" && post_cursor.StartsWith("\n"))
                        {
                            post_cursor = "\r" + post_cursor;
                        }

                        string prevchar;
                        lines.GetLineText(r1, absolute_cursor - linestart - 1, r1, absolute_cursor - linestart + 1, out prevchar);

                        //
                        // this is a fairly pathological case.  but, it does
                        // occasionally happen.  in fact --  the only reason i go to
                        // the trouble to track two cursor_wrapped_offsets is so i can
                        // handle it properly.
                        //
                        if (offsetdelta.Length != 1)
                        {
                            ITextEdit edit = wbuffer.CreateEdit();
                            if (edit == null) continue;
                            
                            edit.Replace(absolute_cursor - 1, 1, offsetdelta);
                            
                            edit.Apply();
                        }
                        else
                        {
                            ITextEdit edit = wbuffer.CreateEdit();
                            if (edit == null) continue;
                            //pre_cursor = "**";
                            //post_cursor = "&&";
                            edit.Replace(linestart, absolute_cursor - linestart - 1, pre_cursor);
                            edit.Replace(absolute_cursor + 1, post_longline.Length, post_cursor);
                            edit.Apply();
                        }

                    }

                    // don't handle any more changes -- one is enough.
                    return;

                }


            }

            }
            catch (Exception e)
            {
                //hrm, that's bad.  i do generate exceptions on occasion.
                string estring = e.ToString();
                MessageBox.Show("Exception in the comment wrapping logic: " + e.ToString());
            }
			
		}

    }



    [Export(typeof(IVsTextViewCreationListener))]
    [ContentType("text")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    public class CreationListener : IVsTextViewCreationListener
    {

        [Import]
        internal IVsEditorAdaptersFactoryService EditorAdaptersFactoryService { get; set; }
        uint connectionCookie;

        
        //[Import]
        //internal IEditorOperationsFactoryService opFactory { get; set; }

        // this gets us access to the DTE, if we need it.
        [Import]
        internal SVsServiceProvider ServiceProvider = null;
         

        //private Dictionary<uint, TextListener> documents;

        private Dictionary<IVsTextView, BaseWrapper> views = new Dictionary<IVsTextView, BaseWrapper>();

        string language_name2(IVsTextLines lines)
        {
            
            Guid guidLangSvc;
            lines.GetLanguageServiceID(out guidLangSvc); // got a Guid of currently used language service.

            Microsoft.VisualStudio.OLE.Interop.IServiceProvider provider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)ServiceProvider.GetService(typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider));

            Guid riid = VSConstants.IID_IUnknown;
            Guid iid = typeof(IVsLanguageInfo).GUID;
            IntPtr ppvObject = IntPtr.Zero;
            int result = provider.QueryService(ref guidLangSvc, ref iid, out ppvObject);
            object langSvc = Marshal.GetObjectForIUnknown(ppvObject);
            IVsLanguageInfo li = langSvc as IVsLanguageInfo;
            if (ppvObject != IntPtr.Zero)
                Marshal.Release(ppvObject);


            string name;
            li.GetLanguageName(out name);
            return name;
        }


        //
        // while lua doesn't have a real language service, it looks
        // like i can still get a IVsCodeWindow for lua files, and thus
        // add dropdown bars for them.  given that i don't have a
        // language service, i just assume that anything with a .lua
        // extension is a lua file..
        //
        bool lua_check2(IVsTextView view)
        {

            // use the view's buffer to figure out the file name, from
            // which we infer whether or not we're looking at a lua file.
            IVsTextLines buffer;
            view.GetBuffer(out buffer);
            IVsUserData data = buffer as IVsUserData;
            if (data == null) return false;
            object nameobj;
            data.GetData(typeof(IVsUserData).GUID, out nameobj);
            if (nameobj == null) return false;
            string fname = nameobj as string;
            if (!fname.ToLower().EndsWith(".lua")) return false;

   
            return true;

        }


        public void VsTextViewCreated(IVsTextView textViewAdapter)
        {
            IVsTextLines lines;
            int rval = textViewAdapter.GetBuffer(out lines);

            if (!views.ContainsKey(textViewAdapter))
            {

                IConnectionPointContainer container = lines as IConnectionPointContainer;
                if (null != container)
                {


                    BaseWrapper listener = null;



                    if (lua_check2(textViewAdapter))
                    {
                       listener = new LuaWrapper(textViewAdapter, EditorAdaptersFactoryService);
                    }
                    else if (language_name2(lines) == "C/C++")
                    {
                        listener = new CppWrapper(textViewAdapter, EditorAdaptersFactoryService);
                    }

                    if (listener!=null)
                    {
                        Guid eventsGuid = typeof(IVsTextLinesEvents).GUID;
                        IConnectionPoint connectionPoint;
                        container.FindConnectionPoint(ref eventsGuid, out connectionPoint);
                        connectionPoint.Advise(listener, out connectionCookie);

                        views[textViewAdapter] = listener;
                        listener.provider = ServiceProvider;
                        listener.pre_change_handling();
                    }



                }
            }
            
        }


    }
}
